This study allows us to revisit/renew

  1. Regression modeling
  2. Properties of Least Squares/Fitting “a line”
  3. Multiple observation

Datasets for this study are

  1. The main file: gauge.txt
  2. Supplementary large-scale files: download the following folder Full Resolution Data.zip More information about the supplementary file can be found at http://iabp.apl.washington.edu/data.html as well as http://nsidc.org/data/G00791

Question

The aim of this lab is to provide a simple procedure for converting gain into density when the gauge is in operation. Keep in mind that the experiment was conducted by varying density and measuring the response in gain, but when the gauge is ultimately in use, the snow-pack density is to be estimated from the measured gain.

Setup

#install.packages('L1pack')
install.packages("quantreg")
trying URL 'https://cran.rstudio.com/bin/macosx/el-capitan/contrib/3.4/quantreg_5.35.tgz'
Content type 'application/x-gzip' length 1863356 bytes (1.8 MB)
==================================================
downloaded 1.8 MB

The downloaded binary packages are in
    /var/folders/_y/_mr2rhx54cb8yyyhqyj2lw3w0000gn/T//RtmpnFn7wt/downloaded_packages

Scenario 1: Fitting

Use the data to fit the gain, or a transformation of gain, to density. Try sketching the least squares line on a scatter plot.

# Plot raw data
title <- 'Density vs. Gain'
x.axis <- expression('Density (g/cm'^3*')')
y.axis <- 'Gain'
x.range <- c(0, .7)
y.range <- c(2.5, 6)
plot(df, main=title, xlab=x.axis, ylab=y.axis, xlim=x.range)

# Take log transformation of response variable (gain)
y.log.axis = 'log(Gain)'
df.log = data.frame(df['density'], log(df['gain']))
plot(df.log, main=title, xlab=x.axis, ylab=y.log.axis, xlim=x.range, ylim=y.range)

# Average replicate measurements
df.log.avg = aggregate(list(gain=df.log$gain), by=list(density=df.log$density), FUN=mean)
plot(df.log.avg, main=title, xlab=x.axis, ylab=y.log.axis, xlim=x.range, ylim=y.range)

# Fit gain to density
cor(df.log.avg)
           density       gain
density  1.0000000 -0.9984469
gain    -0.9984469  1.0000000
least.squares <- lm(gain~density, data=df.log.avg)
lad <- lad(gain~density, data=df.log.avg)
plot(df.log, main=title, xlab=x.axis, ylab=y.log.axis, xlim=x.range, ylim=y.range)
abline(least.squares, col='red')
abline(lad, col='blue')
legend('topright', legend=c('Least Squares Regression Line', 'Least Absolute Deviations Regression Line'), col=c('red', 'blue'), lty=1)

least.squares

Call:
lm(formula = gain ~ density, data = df.log.avg)

Coefficients:
(Intercept)      density  
      5.997       -4.606  
summary(least.squares)

Call:
lm(formula = gain ~ density, data = df.log.avg)

Residuals:
      Min        1Q    Median        3Q       Max 
-0.098150 -0.050047 -0.005898  0.061833  0.075681 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)  5.99727    0.03889  154.22 1.27e-13 ***
density     -4.60594    0.09714  -47.42 4.85e-10 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.06557 on 7 degrees of freedom
Multiple R-squared:  0.9969,    Adjusted R-squared:  0.9965 
F-statistic:  2248 on 1 and 7 DF,  p-value: 4.854e-10
lad
Call:
lad(formula = gain ~ density, data = df.log.avg)
Converged in 4 iterations

Coefficients:
 (Intercept)     density 
     5.9850     -4.5935 

Degrees of freedom: 9 total; 7 residual
Scale estimate: 0.06926379 
summary(lad)
Call:
lad(formula = gain ~ density, data = df.log.avg)

Residuals:
     Min       1Q   Median       3Q      Max 
-0.08870 -0.04178  0.00000  0.07307  0.08038 

Coefficients:
             Estimate Std.Error Z value  p-value 
(Intercept)   5.9850   0.0581  103.0269   0.0000
density      -4.5935   0.1451  -31.6550   0.0000

Degrees of freedom: 9 total; 7 residual
Scale estimate: 0.06926379
Log-likelihood: 11.90934 on 3 degrees of freedom
# Check conditions for linear regression: linearity, normality of residuals, and constant variability
least.squares.residuals <- data.frame(df.log['density'], df.log['gain'] - rep(predict(least.squares), each=10))
lad.residuals <- data.frame(df.log['density'], df.log['gain'] - rep(predict(lad), each=10))
title.residuals1 <- 'Residuals of Least Squares Regression Line'
title.residuals2 <- 'Residuals of Least Absolute Deviations Regression Line'
plot(least.squares.residuals$gain, main=title.residuals1, ylab=y.axis)
abline(0, 0, col='red')

plot(lad.residuals$gain, main=title.residuals2, ylab=y.axis)
abline(0, 0, col='blue')

num.bins <- 12
hist(least.squares.residuals$gain, breaks=num.bins, main=title.residuals1, xlab=y.axis, col='red')

hist(lad.residuals$gain, breaks=num.bins, main=title.residuals2, xlab=y.axis, col='blue')

qqnorm(least.squares.residuals$gain, main=paste('Normal Q-Q Plot with', title.residuals1), cex.main=1)
qqline(least.squares.residuals$gain, col='red')

qqnorm(lad.residuals$gain, main=paste('Normal Q-Q Plot with', title.residuals2), cex.main=1)
qqline(lad.residuals$gain, col='blue')

Scenario 2: Predicting

Ultimately we are interested in answering questions such as: Given a gain reading of 38.6, what is the density of the snow-pack? Given a gain reading of 426.7, what is the density of the snow-pack? These two numeric values, 38.6 and 426.7, were chosen because they are the average gains for the 0.508 and 0.001 densities, respectively.

# Predictions
PredictLogGain <- function(density)
  predict(least.squares, data.frame(density=density))  # Predict log(gain) using density
PredictDensityLeastSquares <- function(gain) {
  intercept <- coef(least.squares)[[1]]
  slope <- coef(least.squares)[[2]]
  (log(gain) - intercept) / slope  # Predict density using gain
}
PredictDensityLad <- function(gain) {
  intercept <- coef(lad)[[1]]
  slope <- coef(lad)[[2]]
  (log(gain) - intercept) / slope  # Predict density using gain
}
PredictDensityQuantile <- function(gain) {
  intercept <- 0
  slope <- 0
  (log(gain) - intercept) / slope  # Predict density using gain
}
# 95% prediction and confidence intervals of log(gain) using density
t <- qt(.975, df=m-2)
mean.density <- mean(df.log.avg$density)
summation <- sum((df.log.avg$density - mean.density) ^ 2)
s2 <- aggregate(list(variance=least.squares.residuals$gain), by=list(density=least.squares.residuals$density), FUN=var)
s.pooled <- sqrt(mean(s2$variance))
center.expr <- quote(center <- PredictLogGain(density))
ci.width.expr <- quote(width <- t * s.pooled * sqrt(1/m + (density-mean.density)^2 / summation))
pi.width.expr <- quote(width <- t * s.pooled * sqrt(1 + 1/m + (density-mean.density)^2 / summation))
LogGainCiLower <- function(density) {
  eval(center.expr)
  eval(ci.width.expr)
  center - width
}
LogGainCiUpper <- function(density) {
  eval(center.expr)
  eval(ci.width.expr)
  center + width
}
LogGainPiLower <- function(density) {
  eval(center.expr)
  eval(pi.width.expr)
  center - width
}
LogGainPiUpper <- function(density) {
  eval(center.expr)
  eval(pi.width.expr)
  center + width
}
# Add bands around least squares line
plot(df.log, main=title, xlab=x.axis, ylab=y.log.axis, xlim=x.range, ylim=y.range)
abline(least.squares, col='red')
ci.col <- 'purple'
pi.col <- 'blue'
symbol <- '-'
size <- 1.5
line.type <- 3
line.width <- 0.7
confidence.intervals <- data.frame(density=df.log.avg$density, lower=LogGainCiLower(df.log.avg$density), upper=LogGainCiUpper(df.log.avg$density))
points(x=confidence.intervals$density, y=confidence.intervals$lower, col=ci.col, pch=symbol, cex=size)
points(x=confidence.intervals$density, y=confidence.intervals$upper, col=ci.col, pch=symbol, cex=size)
lines(x=confidence.intervals$density, y=confidence.intervals$lower, col=ci.col, lty=line.type, lwd=line.width)
lines(x=confidence.intervals$density, y=confidence.intervals$upper, col=ci.col, lty=line.type, lwd=line.width)
prediction.intervals <- data.frame(density=df.log.avg$density, lower=LogGainPiLower(df.log.avg$density), upper=LogGainPiUpper(df.log.avg$density))
points(x=prediction.intervals$density, y=prediction.intervals$lower, col=pi.col, pch=symbol, cex=size)
points(x=prediction.intervals$density, y=prediction.intervals$upper, col=pi.col, pch=symbol, cex=size)
lines(x=prediction.intervals$density, y=prediction.intervals$lower, col=pi.col, lty=line.type, lwd=line.width)
lines(x=prediction.intervals$density, y=prediction.intervals$upper, col=pi.col, lty=line.type, lwd=line.width)
legend('topright', legend=c('Least Squares Regression Line', '95% Confidence Interval Bands', '95% Prediction Interval Bands'), col=c('red', ci.col, pi.col), lty=1)

# 95% prediction and confidence intervals of density using gain
end.points <- c(-1, 3)  # Interval to search the root in
DensityCi <- function(gain) {
  lower <- uniroot(function(density) log(gain) - LogGainCiLower(density), interval=end.points)[[1]]
  upper <- uniroot(function(density) log(gain) - LogGainCiUpper(density), interval=end.points)[[1]]
  c(lower, upper)
}
DensityPi <- function(gain) {
  lower <- uniroot(function(density) log(gain) - LogGainPiLower(density), end.points)[[1]]
  upper <- uniroot(function(density) log(gain) - LogGainPiUpper(density), end.points)[[1]]
  c(lower, upper)
}
# Point and interval estimates for example gain readings
PredictDensityLeastSquares(38.6)  # 38.6 is the average gain for 0.508 density
[1] 0.5089113
PredictDensityLad(38.6)
[1] 0.5076298
#PredictDensityQuantile(38.6)
DensityCi(38.6)
[1] 0.5011879 0.5169000
DensityPi(38.6)
[1] 0.4889568 0.5291323
PredictDensityLeastSquares(426.7)  # 426.7 is the average gain for 0.001 density
[1] -0.01276954
PredictDensityLad(426.7)
[1] -0.01546807
#PredictDensityQuantile(38.6)
DensityCi(426.7)
[1] -0.024285866 -0.001769193
DensityPi(426.7)
[1] -0.034644666  0.008618629

Scenario 3: Cross-Validation

To check how well your procedure works, omit the set of measurements corresponding to the block of density 0.508, apply your “estimation”/calibration procedure to the remaining data, and provide an interval estimate for the density of a block with an average reading of 38.6. Where does the actual density fall in the interval? Try the same test, for the set of measurements at the 0.001 density.

for (omitted in c(0.508, 0.001)) {
  # Omit measurements corresponding to the specified density
  df.log.omitted = df.log[which(df.log['density'] != omitted), ]
  df.log.avg.omitted <- df.log.avg[which(df.log.avg['density'] != omitted), ]
  
  
  # Redo calculations using modified dataset
  least.squares <- lm(gain~density, data=df.log.avg.omitted)
  
  mean.density <- mean(df.log.avg.omitted$density)
  summation <- sum((df.log.avg.omitted$density - mean.density) ^ 2)
  
  s2 <- aggregate(list(variance=least.squares.residuals$gain), by=list(density=least.squares.residuals$density), FUN=var)
  s.pooled <- sqrt(mean(s2$variance))
  
  ci.width.expr <- quote(width <- t * s.pooled * sqrt(1/(m-1) + (density-mean.density)^2 / summation))
  pi.width.expr <- quote(width <- t * s.pooled * sqrt(1 + 1/(m-1) + (density-mean.density)^2 / summation))
  
  plot(df.log.omitted, main=title, xlab=x.axis, ylab=y.log.axis, xlim=x.range, ylim=y.range)
  abline(least.squares, col='red')
  
  ci.col <- 'purple'
  pi.col <- 'blue'
  symbol <- '-'
  size <- 1.5
  line.type <- 3
  line.width <- 0.7
  
  confidence.intervals <- data.frame(density=df.log.avg.omitted$density, lower=LogGainCiLower(df.log.avg.omitted$density), upper=LogGainCiUpper(df.log.avg.omitted$density))
  points(x=confidence.intervals$density, y=confidence.intervals$lower, col=ci.col, pch=symbol, cex=size)
  points(x=confidence.intervals$density, y=confidence.intervals$upper, col=ci.col, pch=symbol, cex=size)
  lines(x=confidence.intervals$density, y=confidence.intervals$lower, col=ci.col, lty=line.type, lwd=line.width)
  lines(x=confidence.intervals$density, y=confidence.intervals$upper, col=ci.col, lty=line.type, lwd=line.width)
  
  prediction.intervals <- data.frame(density=df.log.avg.omitted$density, lower=LogGainPiLower(df.log.avg.omitted$density), upper=LogGainPiUpper(df.log.avg.omitted$density))
  points(x=prediction.intervals$density, y=prediction.intervals$lower, col=pi.col, pch=symbol, cex=size)
  points(x=prediction.intervals$density, y=prediction.intervals$upper, col=pi.col, pch=symbol, cex=size)
  lines(x=prediction.intervals$density, y=prediction.intervals$lower, col=pi.col, lty=line.type, lwd=line.width)
  lines(x=prediction.intervals$density, y=prediction.intervals$upper, col=pi.col, lty=line.type, lwd=line.width)
  
  legend('topright', legend=c('Least Squares Regression Line', '95% Confidence Interval Bands', '95% Prediction Interval Bands'), col=c('red', ci.col, pi.col), lty=1)
  
  print(PredictDensityLeastSquares(38.6))  # 38.6 is the average gain for 0.508 density
  print(DensityCi(38.6))
  print(DensityPi(38.6))
  
  print(PredictDensityLeastSquares(426.7))  # 426.7 is the average gain for 0.001 density
  print(DensityCi(426.7))
  print(DensityPi(426.7))
}
[1] 0.5091927
[1] 0.5006695 0.5180406
[1] 0.4889184 0.5297925
[1] -0.0128045
[1] -0.024342164 -0.001790802
[1] -0.034760736  0.008598777

[1] 0.5092919
[1] 0.5014370 0.5174323
[1] 0.4890257 0.5298473
[1] -0.02051733
[1] -0.035344991 -0.006521825
[1] -0.044604850  0.002738127

Additional Scenario: Temperature, DOY, and Latitude.

Use the additional dataset to construct a model fitting temperature with DOY, latitude, and other reasonable features. Try sketching the least squares line on a scatter plot. We aim to investigate the relationship between temperature and the DOY, and its latitude.

# Check the correlation
data <- read.csv('Full Resolution Data/64506420.csv', header=TRUE)
data <- data[,c('Hour','DOY','POS_DOY','Lat','Lon','Ts','BP')]
# Drop the extreme outlier case
#data <- data[which(data$Ts>-200),]
data_matrix <- as.matrix(data)
# Correlation Matrix
corr_matrix <- cor(data_matrix)
corr_matrix
                Hour          DOY      POS_DOY          Lat         Lon           Ts          BP
Hour     1.000000000 -0.006779489 -0.006775002 -0.007511024  0.01217860  0.008592576  0.02505819
DOY     -0.006779489  1.000000000  0.999999958  0.903704617 -0.68012490 -0.906918658 -0.17232397
POS_DOY -0.006775002  0.999999958  1.000000000  0.903696909 -0.68013445 -0.906916964 -0.17230481
Lat     -0.007511024  0.903704617  0.903696909  1.000000000 -0.59748962 -0.958835395 -0.29666821
Lon      0.012178596 -0.680124899 -0.680134450 -0.597489620  1.00000000  0.564136723 -0.02981279
Ts       0.008592576 -0.906918658 -0.906916964 -0.958835395  0.56413672  1.000000000  0.20223136
BP       0.025058188 -0.172323969 -0.172304805 -0.296668215 -0.02981279  0.202231363  1.00000000
# least squares line
fit<-lm(formula = Ts ~ DOY + Lat, data = data)
summary(fit)

Call:
lm(formula = Ts ~ DOY + Lat, data = data)

Residuals:
     Min       1Q   Median       3Q      Max 
-0.86025 -0.21975 -0.01511  0.22960  0.70988 

Coefficients:
              Estimate Std. Error t value Pr(>|t|)    
(Intercept) 40.0406586  0.5559051   72.03   <2e-16 ***
DOY         -0.0098374  0.0006141  -16.02   <2e-16 ***
Lat         -0.4913977  0.0089025  -55.20   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.3023 on 2066 degrees of freedom
Multiple R-squared:  0.9283,    Adjusted R-squared:  0.9282 
F-statistic: 1.337e+04 on 2 and 2066 DF,  p-value: < 2.2e-16
ggplot(data,aes(x=DOY, y=Ts)) + 
  geom_point(color='#2980B9', size = 4) + 
  geom_smooth(method=lm, color='#2C3E50')

ggplot(data,aes(x=Lat, y=Ts)) + 
  geom_point(color='#2980B9', size = 4) + 
  geom_smooth(method=lm, color='#2C3E50')

title.residuals1 <- 'Residuals of Least Squares Regression Line'
plot(fit$residuals, main=title.residuals1)
abline(0, 0, col='red')

num.bins <- 10
hist(fit$residuals, breaks=num.bins, main=title.residuals1, xlab='Temperature', col='red')

qqnorm(fit$residuals, main=paste('Normal Q-Q Plot with', title.residuals1), cex.main=1)
qqline(fit$residuals, col='red')

# Attempt for Multiple Regressions
data$Midnights <- 0
data$Midnights[which(data$Hour<6)] <- 1
data$Mornings <- 0
data$Mornings[which(data$Hour>=6 & data$Hour<11)] <- 1 
data$Noon <- 0
data$Noon[which(data$Hour>=11 & data$Hour<15)] <- 1
data$Afternoon <- 0
data$Afternoon[which(data$Hour>=15 & data$Hour<20)] <- 1
data$Nights <- 0
data$Nights[which(data$Hour>=20)] <- 1
LS0tCnRpdGxlOiAnQ0FTRSBTVFVEWSA0OicKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKVGhpcyBzdHVkeSBhbGxvd3MgdXMgdG8gcmV2aXNpdC9yZW5ldwoKMS4gUmVncmVzc2lvbiBtb2RlbGluZwoyLiBQcm9wZXJ0aWVzIG9mIExlYXN0IFNxdWFyZXMvRml0dGluZyAiYSBsaW5lIgozLiBNdWx0aXBsZSBvYnNlcnZhdGlvbgoKRGF0YXNldHMgZm9yIHRoaXMgc3R1ZHkgYXJlCgoxLiBUaGUgbWFpbiBmaWxlOiBnYXVnZS50eHQKMi4gU3VwcGxlbWVudGFyeSBsYXJnZS1zY2FsZSBmaWxlczogZG93bmxvYWQgdGhlIGZvbGxvd2luZyBmb2xkZXIgRnVsbCBSZXNvbHV0aW9uIERhdGEuemlwIE1vcmUgaW5mb3JtYXRpb24gYWJvdXQgdGhlIHN1cHBsZW1lbnRhcnkgZmlsZSBjYW4gYmUgZm91bmQgYXQgaHR0cDovL2lhYnAuYXBsLndhc2hpbmd0b24uZWR1L2RhdGEuaHRtbCBhcyB3ZWxsIGFzIGh0dHA6Ly9uc2lkYy5vcmcvZGF0YS9HMDA3OTEKCgojIyBRdWVzdGlvbgpUaGUgYWltIG9mIHRoaXMgbGFiIGlzIHRvIHByb3ZpZGUgYSBzaW1wbGUgcHJvY2VkdXJlIGZvciBjb252ZXJ0aW5nIGdhaW4gaW50byBkZW5zaXR5IHdoZW4gdGhlIGdhdWdlIGlzIGluIG9wZXJhdGlvbi4gS2VlcCBpbiBtaW5kIHRoYXQgdGhlIGV4cGVyaW1lbnQgd2FzIGNvbmR1Y3RlZCBieSB2YXJ5aW5nIGRlbnNpdHkgYW5kIG1lYXN1cmluZyB0aGUgcmVzcG9uc2UgaW4gZ2FpbiwgYnV0IHdoZW4gdGhlIGdhdWdlIGlzIHVsdGltYXRlbHkgaW4gdXNlLCB0aGUgc25vdy1wYWNrIGRlbnNpdHkgaXMgdG8gYmUgZXN0aW1hdGVkIGZyb20gdGhlIG1lYXN1cmVkIGdhaW4uCgoKIyMgU2V0dXAKYGBge3J9CmRmIDwtIHJlYWQudGFibGUoJ2dhdWdlLTF3YjF3YTYtMmdwZWw0MS50eHQnLCBoZWFkZXI9VFJVRSkKZGYgPC0gZGZbb3JkZXIoZGYkZGVuc2l0eSksIF0gICMgU29ydCBmcm9tIGxlYXN0IHRvIGdyZWF0ZXN0IGRlbnNpdHkKCm0gPC0gOSAgIyBOdW1iZXIgb2YgZGlzdGluY3QgYmxvY2sgZGVuc2l0aWVzCnQgPC0gMTAgICMgTnVtYmVyIG9mIHJlcGxpY2F0ZSBtZWFzdXJlbWVudHMKCiNpbnN0YWxsLnBhY2thZ2VzKCdMMXBhY2snKQojaW5zdGFsbC5wYWNrYWdlcygicXVhbnRyZWciKQpsaWJyYXJ5KEwxcGFjaykgICMgVXNlZCBmb3IgbGVhc3QgYWJzb2x1dGUgZGV2aWF0aW9ucyByZWdyZXNzaW9uIGxpbmUKbGlicmFyeShxdWFudHJlZykgICMgVXNlZCBmb3IgcXVhbnRpbGUgcmVncmVzc2lvbiBsaW5lCmBgYAoKCiMjIFNjZW5hcmlvIDE6IEZpdHRpbmcKVXNlIHRoZSBkYXRhIHRvIGZpdCB0aGUgZ2Fpbiwgb3IgYSB0cmFuc2Zvcm1hdGlvbiBvZiBnYWluLCB0byBkZW5zaXR5LiBUcnkgc2tldGNoaW5nIHRoZSBsZWFzdCBzcXVhcmVzIGxpbmUgb24gYSBzY2F0dGVyIHBsb3QuCgoqIERvIHRoZSByZXNpZHVhbHMgaW5kaWNhdGUgYW55IHByb2JsZW1zIHdpdGggdGhlIGZpdD8KKiBJZiB0aGUgZGVuc2l0aWVzIG9mIHRoZSBwb2x5ZXRoeWxlbmUgYmxvY2tzIGFyZSBub3QgcmVwb3J0ZWQgZXhhY3RseSwgaG93IG1pZ2h0IHRoaXMgYWZmZWN0IHRoZSBmaXQ/CiogV2hhdCBpZiB0aGUgYmxvY2tzIG9mIHBvbHlldGh5bGVuZSB3ZXJlIG5vdCBtZWFzdXJlZCBpbiByYW5kb20gb3JkZXIgKGxvY2F0aW9uKT8KYGBge3J9CiMgUGxvdCByYXcgZGF0YQp0aXRsZSA8LSAnRGVuc2l0eSB2cy4gR2FpbicKeC5heGlzIDwtIGV4cHJlc3Npb24oJ0RlbnNpdHkgKGcvY20nXjMqJyknKQp5LmF4aXMgPC0gJ0dhaW4nCngucmFuZ2UgPC0gYygwLCAuNykKeS5yYW5nZSA8LSBjKDIuNSwgNikKcGxvdChkZiwgbWFpbj10aXRsZSwgeGxhYj14LmF4aXMsIHlsYWI9eS5heGlzLCB4bGltPXgucmFuZ2UpCgoKIyBUYWtlIGxvZyB0cmFuc2Zvcm1hdGlvbiBvZiByZXNwb25zZSB2YXJpYWJsZSAoZ2FpbikKeS5sb2cuYXhpcyA9ICdsb2coR2FpbiknCmRmLmxvZyA9IGRhdGEuZnJhbWUoZGZbJ2RlbnNpdHknXSwgbG9nKGRmWydnYWluJ10pKQpwbG90KGRmLmxvZywgbWFpbj10aXRsZSwgeGxhYj14LmF4aXMsIHlsYWI9eS5sb2cuYXhpcywgeGxpbT14LnJhbmdlLCB5bGltPXkucmFuZ2UpCgoKIyBBdmVyYWdlIHJlcGxpY2F0ZSBtZWFzdXJlbWVudHMKZGYubG9nLmF2ZyA9IGFnZ3JlZ2F0ZShsaXN0KGdhaW49ZGYubG9nJGdhaW4pLCBieT1saXN0KGRlbnNpdHk9ZGYubG9nJGRlbnNpdHkpLCBGVU49bWVhbikKcGxvdChkZi5sb2cuYXZnLCBtYWluPXRpdGxlLCB4bGFiPXguYXhpcywgeWxhYj15LmxvZy5heGlzLCB4bGltPXgucmFuZ2UsIHlsaW09eS5yYW5nZSkKCgojIEZpdCBnYWluIHRvIGRlbnNpdHkKY29yKGRmLmxvZy5hdmcpCgpsZWFzdC5zcXVhcmVzIDwtIGxtKGdhaW5+ZGVuc2l0eSwgZGF0YT1kZi5sb2cuYXZnKQpsYWQgPC0gbGFkKGdhaW5+ZGVuc2l0eSwgZGF0YT1kZi5sb2cuYXZnKQoKcGxvdChkZi5sb2csIG1haW49dGl0bGUsIHhsYWI9eC5heGlzLCB5bGFiPXkubG9nLmF4aXMsIHhsaW09eC5yYW5nZSwgeWxpbT15LnJhbmdlKQphYmxpbmUobGVhc3Quc3F1YXJlcywgY29sPSdyZWQnKQphYmxpbmUobGFkLCBjb2w9J2JsdWUnKQpsZWdlbmQoJ3RvcHJpZ2h0JywgbGVnZW5kPWMoJ0xlYXN0IFNxdWFyZXMgUmVncmVzc2lvbiBMaW5lJywgJ0xlYXN0IEFic29sdXRlIERldmlhdGlvbnMgUmVncmVzc2lvbiBMaW5lJyksIGNvbD1jKCdyZWQnLCAnYmx1ZScpLCBsdHk9MSkKCmxlYXN0LnNxdWFyZXMKc3VtbWFyeShsZWFzdC5zcXVhcmVzKQpsYWQKc3VtbWFyeShsYWQpCgoKIyBDaGVjayBjb25kaXRpb25zIGZvciBsaW5lYXIgcmVncmVzc2lvbjogbGluZWFyaXR5LCBub3JtYWxpdHkgb2YgcmVzaWR1YWxzLCBhbmQgY29uc3RhbnQgdmFyaWFiaWxpdHkKbGVhc3Quc3F1YXJlcy5yZXNpZHVhbHMgPC0gZGF0YS5mcmFtZShkZi5sb2dbJ2RlbnNpdHknXSwgZGYubG9nWydnYWluJ10gLSByZXAocHJlZGljdChsZWFzdC5zcXVhcmVzKSwgZWFjaD0xMCkpCmxhZC5yZXNpZHVhbHMgPC0gZGF0YS5mcmFtZShkZi5sb2dbJ2RlbnNpdHknXSwgZGYubG9nWydnYWluJ10gLSByZXAocHJlZGljdChsYWQpLCBlYWNoPTEwKSkKCnRpdGxlLnJlc2lkdWFsczEgPC0gJ1Jlc2lkdWFscyBvZiBMZWFzdCBTcXVhcmVzIFJlZ3Jlc3Npb24gTGluZScKdGl0bGUucmVzaWR1YWxzMiA8LSAnUmVzaWR1YWxzIG9mIExlYXN0IEFic29sdXRlIERldmlhdGlvbnMgUmVncmVzc2lvbiBMaW5lJwpwbG90KGxlYXN0LnNxdWFyZXMucmVzaWR1YWxzJGdhaW4sIG1haW49dGl0bGUucmVzaWR1YWxzMSwgeWxhYj15LmF4aXMpCmFibGluZSgwLCAwLCBjb2w9J3JlZCcpCnBsb3QobGFkLnJlc2lkdWFscyRnYWluLCBtYWluPXRpdGxlLnJlc2lkdWFsczIsIHlsYWI9eS5heGlzKQphYmxpbmUoMCwgMCwgY29sPSdibHVlJykKCm51bS5iaW5zIDwtIDEyCmhpc3QobGVhc3Quc3F1YXJlcy5yZXNpZHVhbHMkZ2FpbiwgYnJlYWtzPW51bS5iaW5zLCBtYWluPXRpdGxlLnJlc2lkdWFsczEsIHhsYWI9eS5heGlzLCBjb2w9J3JlZCcpCmhpc3QobGFkLnJlc2lkdWFscyRnYWluLCBicmVha3M9bnVtLmJpbnMsIG1haW49dGl0bGUucmVzaWR1YWxzMiwgeGxhYj15LmF4aXMsIGNvbD0nYmx1ZScpCgpxcW5vcm0obGVhc3Quc3F1YXJlcy5yZXNpZHVhbHMkZ2FpbiwgbWFpbj1wYXN0ZSgnTm9ybWFsIFEtUSBQbG90IHdpdGgnLCB0aXRsZS5yZXNpZHVhbHMxKSwgY2V4Lm1haW49MSkKcXFsaW5lKGxlYXN0LnNxdWFyZXMucmVzaWR1YWxzJGdhaW4sIGNvbD0ncmVkJykKcXFub3JtKGxhZC5yZXNpZHVhbHMkZ2FpbiwgbWFpbj1wYXN0ZSgnTm9ybWFsIFEtUSBQbG90IHdpdGgnLCB0aXRsZS5yZXNpZHVhbHMyKSwgY2V4Lm1haW49MSkKcXFsaW5lKGxhZC5yZXNpZHVhbHMkZ2FpbiwgY29sPSdibHVlJykKYGBgCgoKIyMgU2NlbmFyaW8gMjogUHJlZGljdGluZwpVbHRpbWF0ZWx5IHdlIGFyZSBpbnRlcmVzdGVkIGluIGFuc3dlcmluZyBxdWVzdGlvbnMgc3VjaCBhczogR2l2ZW4gYSBnYWluIHJlYWRpbmcgb2YgMzguNiwgd2hhdCBpcyB0aGUgZGVuc2l0eSBvZiB0aGUgc25vdy1wYWNrPyBHaXZlbiBhIGdhaW4gcmVhZGluZyBvZiA0MjYuNywgd2hhdCBpcyB0aGUgZGVuc2l0eSBvZiB0aGUgc25vdy1wYWNrPyBUaGVzZSB0d28gbnVtZXJpYyB2YWx1ZXMsIDM4LjYgYW5kIDQyNi43LCB3ZXJlIGNob3NlbiBiZWNhdXNlIHRoZXkgYXJlIHRoZSBhdmVyYWdlIGdhaW5zIGZvciB0aGUgMC41MDggYW5kIDAuMDAxIGRlbnNpdGllcywgcmVzcGVjdGl2ZWx5LgoKKiBEZXZlbG9wIGEgcHJvY2VkdXJlIGZvciBhZGRpbmcgYmFuZHMgYXJvdW5kIHlvdXIgbGVhc3Qgc3F1YXJlcyBsaW5lIHRoYXQgY2FuIGJlIHVzZWQgdG8gbWFrZSBpbnRlcnZhbCBlc3RpbWF0ZXMgZm9yIHRoZSBzbm93LXBhY2sgZGVuc2l0eSBmcm9tIGdhaW4gbWVhc3VyZW1lbnRzLiBLZWVwIGluIG1pbmQgaG93IHRoZSBkYXRhIHdlcmUgY29sbGVjdGVkOiBzZXZlcmFsIG1lYXN1cmVtZW50cyBvZiBnYWluIHdlcmUgdGFrZW4gZm9yIHBvbHllbnl0aHlsZW5lIGJsb2NrcyBvZiBrbm93biBkZW5zaXR5LgpgYGB7ciBmaWcuYXNwPTIsIGZpZy53aWR0aD01fQojIFByZWRpY3Rpb25zClByZWRpY3RMb2dHYWluIDwtIGZ1bmN0aW9uKGRlbnNpdHkpCiAgcHJlZGljdChsZWFzdC5zcXVhcmVzLCBkYXRhLmZyYW1lKGRlbnNpdHk9ZGVuc2l0eSkpICAjIFByZWRpY3QgbG9nKGdhaW4pIHVzaW5nIGRlbnNpdHkKClByZWRpY3REZW5zaXR5TGVhc3RTcXVhcmVzIDwtIGZ1bmN0aW9uKGdhaW4pIHsKICBpbnRlcmNlcHQgPC0gY29lZihsZWFzdC5zcXVhcmVzKVtbMV1dCiAgc2xvcGUgPC0gY29lZihsZWFzdC5zcXVhcmVzKVtbMl1dCiAgKGxvZyhnYWluKSAtIGludGVyY2VwdCkgLyBzbG9wZSAgIyBQcmVkaWN0IGRlbnNpdHkgdXNpbmcgZ2Fpbgp9CgpQcmVkaWN0RGVuc2l0eUxhZCA8LSBmdW5jdGlvbihnYWluKSB7CiAgaW50ZXJjZXB0IDwtIGNvZWYobGFkKVtbMV1dCiAgc2xvcGUgPC0gY29lZihsYWQpW1syXV0KICAobG9nKGdhaW4pIC0gaW50ZXJjZXB0KSAvIHNsb3BlICAjIFByZWRpY3QgZGVuc2l0eSB1c2luZyBnYWluCn0KClByZWRpY3REZW5zaXR5UXVhbnRpbGUgPC0gZnVuY3Rpb24oZ2FpbikgewogIGludGVyY2VwdCA8LSAwCiAgc2xvcGUgPC0gMAogIChsb2coZ2FpbikgLSBpbnRlcmNlcHQpIC8gc2xvcGUgICMgUHJlZGljdCBkZW5zaXR5IHVzaW5nIGdhaW4KfQoKCiMgOTUlIHByZWRpY3Rpb24gYW5kIGNvbmZpZGVuY2UgaW50ZXJ2YWxzIG9mIGxvZyhnYWluKSB1c2luZyBkZW5zaXR5CnQgPC0gcXQoLjk3NSwgZGY9bS0yKQptZWFuLmRlbnNpdHkgPC0gbWVhbihkZi5sb2cuYXZnJGRlbnNpdHkpCnN1bW1hdGlvbiA8LSBzdW0oKGRmLmxvZy5hdmckZGVuc2l0eSAtIG1lYW4uZGVuc2l0eSkgXiAyKQoKczIgPC0gYWdncmVnYXRlKGxpc3QodmFyaWFuY2U9bGVhc3Quc3F1YXJlcy5yZXNpZHVhbHMkZ2FpbiksIGJ5PWxpc3QoZGVuc2l0eT1sZWFzdC5zcXVhcmVzLnJlc2lkdWFscyRkZW5zaXR5KSwgRlVOPXZhcikKcy5wb29sZWQgPC0gc3FydChtZWFuKHMyJHZhcmlhbmNlKSkKCmNlbnRlci5leHByIDwtIHF1b3RlKGNlbnRlciA8LSBQcmVkaWN0TG9nR2FpbihkZW5zaXR5KSkKY2kud2lkdGguZXhwciA8LSBxdW90ZSh3aWR0aCA8LSB0ICogcy5wb29sZWQgKiBzcXJ0KDEvbSArIChkZW5zaXR5LW1lYW4uZGVuc2l0eSleMiAvIHN1bW1hdGlvbikpCnBpLndpZHRoLmV4cHIgPC0gcXVvdGUod2lkdGggPC0gdCAqIHMucG9vbGVkICogc3FydCgxICsgMS9tICsgKGRlbnNpdHktbWVhbi5kZW5zaXR5KV4yIC8gc3VtbWF0aW9uKSkKCkxvZ0dhaW5DaUxvd2VyIDwtIGZ1bmN0aW9uKGRlbnNpdHkpIHsKICBldmFsKGNlbnRlci5leHByKQogIGV2YWwoY2kud2lkdGguZXhwcikKICBjZW50ZXIgLSB3aWR0aAp9CgpMb2dHYWluQ2lVcHBlciA8LSBmdW5jdGlvbihkZW5zaXR5KSB7CiAgZXZhbChjZW50ZXIuZXhwcikKICBldmFsKGNpLndpZHRoLmV4cHIpCiAgY2VudGVyICsgd2lkdGgKfQoKTG9nR2FpblBpTG93ZXIgPC0gZnVuY3Rpb24oZGVuc2l0eSkgewogIGV2YWwoY2VudGVyLmV4cHIpCiAgZXZhbChwaS53aWR0aC5leHByKQogIGNlbnRlciAtIHdpZHRoCn0KCkxvZ0dhaW5QaVVwcGVyIDwtIGZ1bmN0aW9uKGRlbnNpdHkpIHsKICBldmFsKGNlbnRlci5leHByKQogIGV2YWwocGkud2lkdGguZXhwcikKICBjZW50ZXIgKyB3aWR0aAp9CgoKIyBBZGQgYmFuZHMgYXJvdW5kIGxlYXN0IHNxdWFyZXMgbGluZQpwbG90KGRmLmxvZywgbWFpbj10aXRsZSwgeGxhYj14LmF4aXMsIHlsYWI9eS5sb2cuYXhpcywgeGxpbT14LnJhbmdlLCB5bGltPXkucmFuZ2UpCmFibGluZShsZWFzdC5zcXVhcmVzLCBjb2w9J3JlZCcpCgpjaS5jb2wgPC0gJ3B1cnBsZScKcGkuY29sIDwtICdibHVlJwpzeW1ib2wgPC0gJy0nCnNpemUgPC0gMS41CmxpbmUudHlwZSA8LSAzCmxpbmUud2lkdGggPC0gMC43Cgpjb25maWRlbmNlLmludGVydmFscyA8LSBkYXRhLmZyYW1lKGRlbnNpdHk9ZGYubG9nLmF2ZyRkZW5zaXR5LCBsb3dlcj1Mb2dHYWluQ2lMb3dlcihkZi5sb2cuYXZnJGRlbnNpdHkpLCB1cHBlcj1Mb2dHYWluQ2lVcHBlcihkZi5sb2cuYXZnJGRlbnNpdHkpKQpwb2ludHMoeD1jb25maWRlbmNlLmludGVydmFscyRkZW5zaXR5LCB5PWNvbmZpZGVuY2UuaW50ZXJ2YWxzJGxvd2VyLCBjb2w9Y2kuY29sLCBwY2g9c3ltYm9sLCBjZXg9c2l6ZSkKcG9pbnRzKHg9Y29uZmlkZW5jZS5pbnRlcnZhbHMkZGVuc2l0eSwgeT1jb25maWRlbmNlLmludGVydmFscyR1cHBlciwgY29sPWNpLmNvbCwgcGNoPXN5bWJvbCwgY2V4PXNpemUpCmxpbmVzKHg9Y29uZmlkZW5jZS5pbnRlcnZhbHMkZGVuc2l0eSwgeT1jb25maWRlbmNlLmludGVydmFscyRsb3dlciwgY29sPWNpLmNvbCwgbHR5PWxpbmUudHlwZSwgbHdkPWxpbmUud2lkdGgpCmxpbmVzKHg9Y29uZmlkZW5jZS5pbnRlcnZhbHMkZGVuc2l0eSwgeT1jb25maWRlbmNlLmludGVydmFscyR1cHBlciwgY29sPWNpLmNvbCwgbHR5PWxpbmUudHlwZSwgbHdkPWxpbmUud2lkdGgpCgpwcmVkaWN0aW9uLmludGVydmFscyA8LSBkYXRhLmZyYW1lKGRlbnNpdHk9ZGYubG9nLmF2ZyRkZW5zaXR5LCBsb3dlcj1Mb2dHYWluUGlMb3dlcihkZi5sb2cuYXZnJGRlbnNpdHkpLCB1cHBlcj1Mb2dHYWluUGlVcHBlcihkZi5sb2cuYXZnJGRlbnNpdHkpKQpwb2ludHMoeD1wcmVkaWN0aW9uLmludGVydmFscyRkZW5zaXR5LCB5PXByZWRpY3Rpb24uaW50ZXJ2YWxzJGxvd2VyLCBjb2w9cGkuY29sLCBwY2g9c3ltYm9sLCBjZXg9c2l6ZSkKcG9pbnRzKHg9cHJlZGljdGlvbi5pbnRlcnZhbHMkZGVuc2l0eSwgeT1wcmVkaWN0aW9uLmludGVydmFscyR1cHBlciwgY29sPXBpLmNvbCwgcGNoPXN5bWJvbCwgY2V4PXNpemUpCmxpbmVzKHg9cHJlZGljdGlvbi5pbnRlcnZhbHMkZGVuc2l0eSwgeT1wcmVkaWN0aW9uLmludGVydmFscyRsb3dlciwgY29sPXBpLmNvbCwgbHR5PWxpbmUudHlwZSwgbHdkPWxpbmUud2lkdGgpCmxpbmVzKHg9cHJlZGljdGlvbi5pbnRlcnZhbHMkZGVuc2l0eSwgeT1wcmVkaWN0aW9uLmludGVydmFscyR1cHBlciwgY29sPXBpLmNvbCwgbHR5PWxpbmUudHlwZSwgbHdkPWxpbmUud2lkdGgpCgpsZWdlbmQoJ3RvcHJpZ2h0JywgbGVnZW5kPWMoJ0xlYXN0IFNxdWFyZXMgUmVncmVzc2lvbiBMaW5lJywgJzk1JSBDb25maWRlbmNlIEludGVydmFsIEJhbmRzJywgJzk1JSBQcmVkaWN0aW9uIEludGVydmFsIEJhbmRzJyksIGNvbD1jKCdyZWQnLCBjaS5jb2wsIHBpLmNvbCksIGx0eT0xKQoKCiMgOTUlIHByZWRpY3Rpb24gYW5kIGNvbmZpZGVuY2UgaW50ZXJ2YWxzIG9mIGRlbnNpdHkgdXNpbmcgZ2FpbgplbmQucG9pbnRzIDwtIGMoLTEsIDMpICAjIEludGVydmFsIHRvIHNlYXJjaCB0aGUgcm9vdCBpbgoKRGVuc2l0eUNpIDwtIGZ1bmN0aW9uKGdhaW4pIHsKICBsb3dlciA8LSB1bmlyb290KGZ1bmN0aW9uKGRlbnNpdHkpIGxvZyhnYWluKSAtIExvZ0dhaW5DaUxvd2VyKGRlbnNpdHkpLCBpbnRlcnZhbD1lbmQucG9pbnRzKVtbMV1dCiAgdXBwZXIgPC0gdW5pcm9vdChmdW5jdGlvbihkZW5zaXR5KSBsb2coZ2FpbikgLSBMb2dHYWluQ2lVcHBlcihkZW5zaXR5KSwgaW50ZXJ2YWw9ZW5kLnBvaW50cylbWzFdXQogIGMobG93ZXIsIHVwcGVyKQp9CgpEZW5zaXR5UGkgPC0gZnVuY3Rpb24oZ2FpbikgewogIGxvd2VyIDwtIHVuaXJvb3QoZnVuY3Rpb24oZGVuc2l0eSkgbG9nKGdhaW4pIC0gTG9nR2FpblBpTG93ZXIoZGVuc2l0eSksIGVuZC5wb2ludHMpW1sxXV0KICB1cHBlciA8LSB1bmlyb290KGZ1bmN0aW9uKGRlbnNpdHkpIGxvZyhnYWluKSAtIExvZ0dhaW5QaVVwcGVyKGRlbnNpdHkpLCBlbmQucG9pbnRzKVtbMV1dCiAgYyhsb3dlciwgdXBwZXIpCn0KCgojIFBvaW50IGFuZCBpbnRlcnZhbCBlc3RpbWF0ZXMgZm9yIGV4YW1wbGUgZ2FpbiByZWFkaW5ncwpQcmVkaWN0RGVuc2l0eUxlYXN0U3F1YXJlcygzOC42KSAgIyAzOC42IGlzIHRoZSBhdmVyYWdlIGdhaW4gZm9yIDAuNTA4IGRlbnNpdHkKUHJlZGljdERlbnNpdHlMYWQoMzguNikKI1ByZWRpY3REZW5zaXR5UXVhbnRpbGUoMzguNikKRGVuc2l0eUNpKDM4LjYpCkRlbnNpdHlQaSgzOC42KQoKUHJlZGljdERlbnNpdHlMZWFzdFNxdWFyZXMoNDI2LjcpICAjIDQyNi43IGlzIHRoZSBhdmVyYWdlIGdhaW4gZm9yIDAuMDAxIGRlbnNpdHkKUHJlZGljdERlbnNpdHlMYWQoNDI2LjcpCiNQcmVkaWN0RGVuc2l0eVF1YW50aWxlKDM4LjYpCkRlbnNpdHlDaSg0MjYuNykKRGVuc2l0eVBpKDQyNi43KQpgYGAKCgojIyBTY2VuYXJpbyAzOiBDcm9zcy1WYWxpZGF0aW9uClRvIGNoZWNrIGhvdyB3ZWxsIHlvdXIgcHJvY2VkdXJlIHdvcmtzLCBvbWl0IHRoZSBzZXQgb2YgbWVhc3VyZW1lbnRzIGNvcnJlc3BvbmRpbmcgdG8gdGhlIGJsb2NrIG9mIGRlbnNpdHkgMC41MDgsIGFwcGx5IHlvdXIgImVzdGltYXRpb24iL2NhbGlicmF0aW9uIHByb2NlZHVyZSB0byB0aGUgcmVtYWluaW5nIGRhdGEsIGFuZCBwcm92aWRlIGFuIGludGVydmFsIGVzdGltYXRlIGZvciB0aGUgZGVuc2l0eSBvZiBhIGJsb2NrIHdpdGggYW4gYXZlcmFnZSByZWFkaW5nIG9mIDM4LjYuIFdoZXJlIGRvZXMgdGhlIGFjdHVhbCBkZW5zaXR5IGZhbGwgaW4gdGhlIGludGVydmFsPyBUcnkgdGhlIHNhbWUgdGVzdCwgZm9yIHRoZSBzZXQgb2YgbWVhc3VyZW1lbnRzIGF0IHRoZSAwLjAwMSBkZW5zaXR5LgpgYGB7ciBmaWcuYXNwPTIsIGZpZy53aWR0aD01fQpmb3IgKG9taXR0ZWQgaW4gYygwLjUwOCwgMC4wMDEpKSB7CiAgIyBPbWl0IG1lYXN1cmVtZW50cyBjb3JyZXNwb25kaW5nIHRvIHRoZSBzcGVjaWZpZWQgZGVuc2l0eQogIGRmLmxvZy5vbWl0dGVkID0gZGYubG9nW3doaWNoKGRmLmxvZ1snZGVuc2l0eSddICE9IG9taXR0ZWQpLCBdCiAgZGYubG9nLmF2Zy5vbWl0dGVkIDwtIGRmLmxvZy5hdmdbd2hpY2goZGYubG9nLmF2Z1snZGVuc2l0eSddICE9IG9taXR0ZWQpLCBdCiAgCiAgCiAgIyBSZWRvIGNhbGN1bGF0aW9ucyB1c2luZyBtb2RpZmllZCBkYXRhc2V0CiAgbGVhc3Quc3F1YXJlcyA8LSBsbShnYWlufmRlbnNpdHksIGRhdGE9ZGYubG9nLmF2Zy5vbWl0dGVkKQogIAogIG1lYW4uZGVuc2l0eSA8LSBtZWFuKGRmLmxvZy5hdmcub21pdHRlZCRkZW5zaXR5KQogIHN1bW1hdGlvbiA8LSBzdW0oKGRmLmxvZy5hdmcub21pdHRlZCRkZW5zaXR5IC0gbWVhbi5kZW5zaXR5KSBeIDIpCiAgCiAgczIgPC0gYWdncmVnYXRlKGxpc3QodmFyaWFuY2U9bGVhc3Quc3F1YXJlcy5yZXNpZHVhbHMkZ2FpbiksIGJ5PWxpc3QoZGVuc2l0eT1sZWFzdC5zcXVhcmVzLnJlc2lkdWFscyRkZW5zaXR5KSwgRlVOPXZhcikKICBzLnBvb2xlZCA8LSBzcXJ0KG1lYW4oczIkdmFyaWFuY2UpKQogIAogIGNpLndpZHRoLmV4cHIgPC0gcXVvdGUod2lkdGggPC0gdCAqIHMucG9vbGVkICogc3FydCgxLyhtLTEpICsgKGRlbnNpdHktbWVhbi5kZW5zaXR5KV4yIC8gc3VtbWF0aW9uKSkKICBwaS53aWR0aC5leHByIDwtIHF1b3RlKHdpZHRoIDwtIHQgKiBzLnBvb2xlZCAqIHNxcnQoMSArIDEvKG0tMSkgKyAoZGVuc2l0eS1tZWFuLmRlbnNpdHkpXjIgLyBzdW1tYXRpb24pKQogIAogIHBsb3QoZGYubG9nLm9taXR0ZWQsIG1haW49dGl0bGUsIHhsYWI9eC5heGlzLCB5bGFiPXkubG9nLmF4aXMsIHhsaW09eC5yYW5nZSwgeWxpbT15LnJhbmdlKQogIGFibGluZShsZWFzdC5zcXVhcmVzLCBjb2w9J3JlZCcpCiAgCiAgY2kuY29sIDwtICdwdXJwbGUnCiAgcGkuY29sIDwtICdibHVlJwogIHN5bWJvbCA8LSAnLScKICBzaXplIDwtIDEuNQogIGxpbmUudHlwZSA8LSAzCiAgbGluZS53aWR0aCA8LSAwLjcKICAKICBjb25maWRlbmNlLmludGVydmFscyA8LSBkYXRhLmZyYW1lKGRlbnNpdHk9ZGYubG9nLmF2Zy5vbWl0dGVkJGRlbnNpdHksIGxvd2VyPUxvZ0dhaW5DaUxvd2VyKGRmLmxvZy5hdmcub21pdHRlZCRkZW5zaXR5KSwgdXBwZXI9TG9nR2FpbkNpVXBwZXIoZGYubG9nLmF2Zy5vbWl0dGVkJGRlbnNpdHkpKQogIHBvaW50cyh4PWNvbmZpZGVuY2UuaW50ZXJ2YWxzJGRlbnNpdHksIHk9Y29uZmlkZW5jZS5pbnRlcnZhbHMkbG93ZXIsIGNvbD1jaS5jb2wsIHBjaD1zeW1ib2wsIGNleD1zaXplKQogIHBvaW50cyh4PWNvbmZpZGVuY2UuaW50ZXJ2YWxzJGRlbnNpdHksIHk9Y29uZmlkZW5jZS5pbnRlcnZhbHMkdXBwZXIsIGNvbD1jaS5jb2wsIHBjaD1zeW1ib2wsIGNleD1zaXplKQogIGxpbmVzKHg9Y29uZmlkZW5jZS5pbnRlcnZhbHMkZGVuc2l0eSwgeT1jb25maWRlbmNlLmludGVydmFscyRsb3dlciwgY29sPWNpLmNvbCwgbHR5PWxpbmUudHlwZSwgbHdkPWxpbmUud2lkdGgpCiAgbGluZXMoeD1jb25maWRlbmNlLmludGVydmFscyRkZW5zaXR5LCB5PWNvbmZpZGVuY2UuaW50ZXJ2YWxzJHVwcGVyLCBjb2w9Y2kuY29sLCBsdHk9bGluZS50eXBlLCBsd2Q9bGluZS53aWR0aCkKICAKICBwcmVkaWN0aW9uLmludGVydmFscyA8LSBkYXRhLmZyYW1lKGRlbnNpdHk9ZGYubG9nLmF2Zy5vbWl0dGVkJGRlbnNpdHksIGxvd2VyPUxvZ0dhaW5QaUxvd2VyKGRmLmxvZy5hdmcub21pdHRlZCRkZW5zaXR5KSwgdXBwZXI9TG9nR2FpblBpVXBwZXIoZGYubG9nLmF2Zy5vbWl0dGVkJGRlbnNpdHkpKQogIHBvaW50cyh4PXByZWRpY3Rpb24uaW50ZXJ2YWxzJGRlbnNpdHksIHk9cHJlZGljdGlvbi5pbnRlcnZhbHMkbG93ZXIsIGNvbD1waS5jb2wsIHBjaD1zeW1ib2wsIGNleD1zaXplKQogIHBvaW50cyh4PXByZWRpY3Rpb24uaW50ZXJ2YWxzJGRlbnNpdHksIHk9cHJlZGljdGlvbi5pbnRlcnZhbHMkdXBwZXIsIGNvbD1waS5jb2wsIHBjaD1zeW1ib2wsIGNleD1zaXplKQogIGxpbmVzKHg9cHJlZGljdGlvbi5pbnRlcnZhbHMkZGVuc2l0eSwgeT1wcmVkaWN0aW9uLmludGVydmFscyRsb3dlciwgY29sPXBpLmNvbCwgbHR5PWxpbmUudHlwZSwgbHdkPWxpbmUud2lkdGgpCiAgbGluZXMoeD1wcmVkaWN0aW9uLmludGVydmFscyRkZW5zaXR5LCB5PXByZWRpY3Rpb24uaW50ZXJ2YWxzJHVwcGVyLCBjb2w9cGkuY29sLCBsdHk9bGluZS50eXBlLCBsd2Q9bGluZS53aWR0aCkKICAKICBsZWdlbmQoJ3RvcHJpZ2h0JywgbGVnZW5kPWMoJ0xlYXN0IFNxdWFyZXMgUmVncmVzc2lvbiBMaW5lJywgJzk1JSBDb25maWRlbmNlIEludGVydmFsIEJhbmRzJywgJzk1JSBQcmVkaWN0aW9uIEludGVydmFsIEJhbmRzJyksIGNvbD1jKCdyZWQnLCBjaS5jb2wsIHBpLmNvbCksIGx0eT0xKQogIAogIHByaW50KFByZWRpY3REZW5zaXR5TGVhc3RTcXVhcmVzKDM4LjYpKSAgIyAzOC42IGlzIHRoZSBhdmVyYWdlIGdhaW4gZm9yIDAuNTA4IGRlbnNpdHkKICBwcmludChEZW5zaXR5Q2koMzguNikpCiAgcHJpbnQoRGVuc2l0eVBpKDM4LjYpKQogIAogIHByaW50KFByZWRpY3REZW5zaXR5TGVhc3RTcXVhcmVzKDQyNi43KSkgICMgNDI2LjcgaXMgdGhlIGF2ZXJhZ2UgZ2FpbiBmb3IgMC4wMDEgZGVuc2l0eQogIHByaW50KERlbnNpdHlDaSg0MjYuNykpCiAgcHJpbnQoRGVuc2l0eVBpKDQyNi43KSkKfQpgYGAKCgojIyBBZGRpdGlvbmFsIFNjZW5hcmlvOiBUZW1wZXJhdHVyZSwgRE9ZLCBhbmQgTGF0aXR1ZGUuClVzZSB0aGUgYWRkaXRpb25hbCBkYXRhc2V0IHRvIGNvbnN0cnVjdCBhIG1vZGVsIGZpdHRpbmcgdGVtcGVyYXR1cmUgd2l0aCBET1ksIGxhdGl0dWRlLCBhbmQgb3RoZXIgcmVhc29uYWJsZSBmZWF0dXJlcy4gVHJ5IHNrZXRjaGluZyB0aGUgbGVhc3Qgc3F1YXJlcyBsaW5lIG9uIGEgc2NhdHRlciBwbG90LiBXZSBhaW0gdG8gaW52ZXN0aWdhdGUgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRlbXBlcmF0dXJlIGFuZCB0aGUgRE9ZLCBhbmQgaXRzIGxhdGl0dWRlLgpgYGB7cn0KIyBDaGVjayB0aGUgY29ycmVsYXRpb24KZGF0YSA8LSByZWFkLmNzdignRnVsbCBSZXNvbHV0aW9uIERhdGEvNjQ1MDY0MjAuY3N2JywgaGVhZGVyPVRSVUUpCmRhdGEgPC0gZGF0YVssYygnSG91cicsJ0RPWScsJ1BPU19ET1knLCdMYXQnLCdMb24nLCdUcycsJ0JQJyldCgojIERyb3AgdGhlIGV4dHJlbWUgb3V0bGllciBjYXNlCiNkYXRhIDwtIGRhdGFbd2hpY2goZGF0YSRUcz4tMjAwKSxdCmRhdGFfbWF0cml4IDwtIGFzLm1hdHJpeChkYXRhKQoKIyBDb3JyZWxhdGlvbiBNYXRyaXgKY29ycl9tYXRyaXggPC0gY29yKGRhdGFfbWF0cml4KQpjb3JyX21hdHJpeApgYGAKCmBgYHtyfQojIGxlYXN0IHNxdWFyZXMgbGluZQpmaXQ8LWxtKGZvcm11bGEgPSBUcyB+IERPWSArIExhdCwgZGF0YSA9IGRhdGEpCnN1bW1hcnkoZml0KQoKZ2dwbG90KGRhdGEsYWVzKHg9RE9ZLCB5PVRzKSkgKyAKICBnZW9tX3BvaW50KGNvbG9yPScjMjk4MEI5Jywgc2l6ZSA9IDQpICsgCiAgZ2VvbV9zbW9vdGgobWV0aG9kPWxtLCBjb2xvcj0nIzJDM0U1MCcpCgpnZ3Bsb3QoZGF0YSxhZXMoeD1MYXQsIHk9VHMpKSArIAogIGdlb21fcG9pbnQoY29sb3I9JyMyOTgwQjknLCBzaXplID0gNCkgKyAKICBnZW9tX3Ntb290aChtZXRob2Q9bG0sIGNvbG9yPScjMkMzRTUwJykKCnRpdGxlLnJlc2lkdWFsczEgPC0gJ1Jlc2lkdWFscyBvZiBMZWFzdCBTcXVhcmVzIFJlZ3Jlc3Npb24gTGluZScKcGxvdChmaXQkcmVzaWR1YWxzLCBtYWluPXRpdGxlLnJlc2lkdWFsczEpCmFibGluZSgwLCAwLCBjb2w9J3JlZCcpCgpudW0uYmlucyA8LSAxMApoaXN0KGZpdCRyZXNpZHVhbHMsIGJyZWFrcz1udW0uYmlucywgbWFpbj10aXRsZS5yZXNpZHVhbHMxLCB4bGFiPSdUZW1wZXJhdHVyZScsIGNvbD0ncmVkJykKCnFxbm9ybShmaXQkcmVzaWR1YWxzLCBtYWluPXBhc3RlKCdOb3JtYWwgUS1RIFBsb3Qgd2l0aCcsIHRpdGxlLnJlc2lkdWFsczEpLCBjZXgubWFpbj0xKQpxcWxpbmUoZml0JHJlc2lkdWFscywgY29sPSdyZWQnKQpgYGAKCmBgYHtyfQojIEF0dGVtcHQgZm9yIE11bHRpcGxlIFJlZ3Jlc3Npb25zCmRhdGEkTWlkbmlnaHRzIDwtIDAKZGF0YSRNaWRuaWdodHNbd2hpY2goZGF0YSRIb3VyPDYpXSA8LSAxCmRhdGEkTW9ybmluZ3MgPC0gMApkYXRhJE1vcm5pbmdzW3doaWNoKGRhdGEkSG91cj49NiAmIGRhdGEkSG91cjwxMSldIDwtIDEgCmRhdGEkTm9vbiA8LSAwCmRhdGEkTm9vblt3aGljaChkYXRhJEhvdXI+PTExICYgZGF0YSRIb3VyPDE1KV0gPC0gMQpkYXRhJEFmdGVybm9vbiA8LSAwCmRhdGEkQWZ0ZXJub29uW3doaWNoKGRhdGEkSG91cj49MTUgJiBkYXRhJEhvdXI8MjApXSA8LSAxCmRhdGEkTmlnaHRzIDwtIDAKZGF0YSROaWdodHNbd2hpY2goZGF0YSRIb3VyPj0yMCldIDwtIDEKYGBg